package cn.edu.sdjzu.knoverse.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.edu.sdjzu.knoverse.constant.MinioConstant;
import cn.edu.sdjzu.knoverse.constant.RedisUserConstant;
import cn.edu.sdjzu.knoverse.constant.SaSessionConstant;
import cn.edu.sdjzu.knoverse.mapper.RoleMapper;
import cn.edu.sdjzu.knoverse.mapper.UserDeptMapper;
import cn.edu.sdjzu.knoverse.mapper.UserMapper;
import cn.edu.sdjzu.knoverse.mapper.UserRoleMapper;
import cn.edu.sdjzu.knoverse.model.dto.system.files.FileUploadDto;
import cn.edu.sdjzu.knoverse.model.dto.system.user.AdminUserAddDto;
import cn.edu.sdjzu.knoverse.model.dto.system.user.AdminUserDto;
import cn.edu.sdjzu.knoverse.model.dto.system.user.AdminUserUpdateByLocalUserDto;
import cn.edu.sdjzu.knoverse.model.dto.system.user.AdminUserUpdateDto;
import cn.edu.sdjzu.knoverse.model.dto.system.user.AdminUserUpdateUserStatusDto;
import cn.edu.sdjzu.knoverse.model.dto.system.user.AdminUserUpdateWithPasswordDto;
import cn.edu.sdjzu.knoverse.model.dto.system.user.LoginDto;
import cn.edu.sdjzu.knoverse.model.dto.system.user.UserPasswordUpdateDto;
import cn.edu.sdjzu.knoverse.model.dto.system.user.UserUpdateWithAvatarDto;
import cn.edu.sdjzu.knoverse.model.entity.kg.UserNode;
import cn.edu.sdjzu.knoverse.model.entity.system.Role;
import cn.edu.sdjzu.knoverse.model.entity.system.User;
import cn.edu.sdjzu.knoverse.model.entity.system.UserDept;
import cn.edu.sdjzu.knoverse.model.enums.LoginEnums;
import cn.edu.sdjzu.knoverse.model.minio.MinioFilePath;
import cn.edu.sdjzu.knoverse.model.views.ViewUserDept;
import cn.edu.sdjzu.knoverse.model.vo.result.PageResult;
import cn.edu.sdjzu.knoverse.model.vo.result.ResultCodeEnum;
import cn.edu.sdjzu.knoverse.model.vo.system.files.FileInfoVo;
import cn.edu.sdjzu.knoverse.model.vo.system.user.AdminUserVo;
import cn.edu.sdjzu.knoverse.model.vo.system.user.LoginVo;
import cn.edu.sdjzu.knoverse.model.vo.system.user.SearchUserinfoVo;
import cn.edu.sdjzu.knoverse.model.vo.system.user.UserVo;
import cn.edu.sdjzu.knoverse.repository.UserRepository;
import cn.edu.sdjzu.knoverse.service.FilesService;
import cn.edu.sdjzu.knoverse.service.UserService;
import cn.edu.sdjzu.knoverse.utils.PasswordEncoder;
import cn.edu.sdjzu.knoverse.utils.UserUtil;
import cn.edu.sdjzu.knoverse.utils.context.BaseContext;
import cn.edu.sdjzu.knoverse.utils.exception.CustomException;
import cn.edu.sdjzu.knoverse.utils.login.DefaultLoginStrategy;
import cn.edu.sdjzu.knoverse.utils.login.LoginContext;
import cn.edu.sdjzu.knoverse.utils.login.LoginStrategy;
import cn.edu.sdjzu.knoverse.utils.minio.MinioUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.validation.Valid;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;

import cn.edu.sdjzu.knoverse.excel.UserImportListener;
import cn.edu.sdjzu.knoverse.model.dto.system.user.UserImportExcelDto;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;

/**
 * 用户信息 服务实现类
 */
@Slf4j
@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserUtil userUtil;
    @Autowired
    private UserDeptMapper userDeptMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    UserRepository userRepository;
    @Autowired
    private MinioUtil minioUtil;
    /**
     * 前台用户登录接口
     * 这里不用判断用户是否为空，因为在登录时已经校验过了
     * <p>
     * 抛出异常使用自带的 UsernameNotFoundException 或者自己封装<br/>
     * 但是这两个效果传入参数都是一样的，所以全部使用 UsernameNotFoundException
     * </p>
     *
     * @param loginDto 登录参数
     * @return 登录后结果返回
     */
    @Override
    public LoginVo login(LoginDto loginDto) {
        int readMeDay = loginDto.getReadMeDay();

        // 初始化登录策略，如果有需要添加策略放在这里
        HashMap<String, LoginStrategy> loginStrategyHashMap = new HashMap<>();
        loginStrategyHashMap.put(LoginEnums.default_STRATEGY.getValue(), new DefaultLoginStrategy(userMapper, passwordEncoder));

        // 使用登录上下文调用登录策略
        LoginContext loginContext = new LoginContext(loginStrategyHashMap);
        User user = loginContext.executeStrategy(loginDto);

        // 判断用户是否禁用
        if (user.getStatus()) {
            throw new CustomException(ResultCodeEnum.FAIL_NO_ACCESS_DENIED_USER_LOCKED);
        }
        LoginVo loginVo = userUtil.buildLoginUserVo(user, readMeDay);
        // 这里获取的是当前账号 id 的 Account-Session (必须是登录后才能调用)
        // 也有一个 getAccountSessionByToken(token) 方法，获取的是指定 token 的 Account-Session
        StpUtil.getSession().set(SaSessionConstant.USER, loginVo);
        return loginVo;
    }

    /**
     * 退出登录
     */
    @Override
    public void logout() {
        // sa-token 退出登录，删除session
        StpUtil.getSession().clear();
        StpUtil.logout();
    }

    /**
     * * 获取用户信息
     *
     * @param id 用户id
     * @return 用户信息
     */
    @Override
    public UserVo getUserinfoById(Long id) {
        // 判断请求Id是否为空
        if (id == null) throw new CustomException(ResultCodeEnum.REQUEST_IS_EMPTY);
        User user = getById(id);

        // 用户是否存在
        if (user == null) throw new CustomException(ResultCodeEnum.DATA_NOT_EXIST);

        // 用户头像
        String avatar = user.getAvatar();
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user, userVo);

        userVo.setAvatar(userUtil.checkGetUserAvatar(avatar));
        return userVo;
    }

    /**
     * * 管理员修改管理员用户密码
     *
     * @param dto 管理员用户修改密码
     */
    @Override
    public void updateUserPasswordByAdmin(AdminUserUpdateWithPasswordDto dto) {
        Long userId = dto.getUserId();
        String password = dto.getPassword();

        // 对密码加密
        String encode = passwordEncoder.encode(password);
        User adminUser = getOne(Wrappers.<User>lambdaQuery().eq(User::getId, userId));

        // 判断是否存在这个用户
        if (adminUser == null) throw new CustomException(ResultCodeEnum.USER_IS_EMPTY);

        // 判断新密码是否与旧密码相同
        if (adminUser.getPassword().equals(encode))
            throw new CustomException(ResultCodeEnum.UPDATE_NEW_PASSWORD_SAME_AS_OLD_PASSWORD);

        // 更新用户密码
        adminUser = new User();
        adminUser.setPassword(encode);
        adminUser.setId(userId);
        updateById(adminUser);

        // 删除登录用户信息
        StpUtil.logout(userId);
    }

    /**
     * * 管理员上传用户头像
     *
     * @param dto 管理员用户修改头像
     */
    @SneakyThrows
    @Override
    public void uploadAvatarByAdmin(UserUpdateWithAvatarDto dto) {
        MultipartFile avatar = dto.getAvatar();
        Long userId = dto.getUserId();

        // 判断是否存在这个用户
        User user = getOne(Wrappers.<User>lambdaQuery().eq(User::getId, userId));
        if (user == null) throw new CustomException(ResultCodeEnum.USER_IS_EMPTY);

        // 直接使用MinioUtil上传头像，不使用filesService.upload
        MinioFilePath minioFilePath = minioUtil.uploadObjectReturnFilePath(avatar, MinioConstant.avatar);
        String avatarPath = minioFilePath.getBucketNameFilepath();

        // 更新用户
        User adminUser = new User();
        adminUser.setId(userId);
        adminUser.setAvatar(avatarPath);
        updateById(adminUser);

        // 重新生成用户信息到Redis中
        user.setAvatar(adminUser.getAvatar());
        userUtil.buildUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME);
    }

    /**
     * * 强制退出
     *
     * @param id 用户id
     */
    @Override
    public void forcedOffline(Long id) {
        if (id == null) {
            throw new CustomException(ResultCodeEnum.REQUEST_IS_EMPTY);
        }
        try {
            StpUtil.kickout(id);
        } catch (Exception e) {
            // 在日志中记录异常，但不中断流程
            log.error("强制用户 {} 下线时发生错误: {}", id, e.getMessage());
        }
    }

    /**
     * * 查询用户
     *
     * @param keyword 查询用户信息关键字
     * @return 用户信息列表
     */
    @Override
    public List<SearchUserinfoVo> queryUser(String keyword) {
        if (!StringUtils.hasText(keyword)) {
            return list(Page.of(1, 20), Wrappers.<User>lambdaQuery().eq(User::getStatus, false)).stream().map(adminUser -> {
                SearchUserinfoVo adminUserVo = new SearchUserinfoVo();
                BeanUtils.copyProperties(adminUser, adminUserVo);
                return adminUserVo;
            }).toList();
        }

        List<User> list = baseMapper.queryUser(keyword);
        return list.stream().map(adminUser -> {
            SearchUserinfoVo adminUserVo = new SearchUserinfoVo();
            BeanUtils.copyProperties(adminUser, adminUserVo);
            return adminUserVo;
        }).toList();
    }

    /**
     * * 修改用户状态
     *
     * @param dto 管理员用户修改状态
     */
    @Override
    public void updateUserStatusByAdmin(AdminUserUpdateUserStatusDto dto) {
        Long userId = dto.getUserId();

        // 更新用户Id
        User adminUser = new User();
        adminUser.setId(userId);
        adminUser.setStatus(dto.getStatus());
        updateById(adminUser);
    }

    /**
     * * 获取本地登录用户信息
     *
     * @return 用户信息
     */
    @Override
    public LoginVo getUserinfo() {
        return BaseContext.getLoginVo();
    }

    /**
     * * 更新本地用户信息
     *
     * @param dto 用户信息
     */
    @Override
    public void updateAdminUserByLocalUser(AdminUserUpdateByLocalUserDto dto) {
        Long userId = BaseContext.getUserId();

        // 判断是否存在这个用户
        User user = getOne(Wrappers.<User>lambdaQuery().eq(User::getId, userId));
        if (user == null) throw new CustomException(ResultCodeEnum.USER_IS_EMPTY);

        // 更新用户
        User adminUser = new User();
        adminUser.setId(userId);
        BeanUtils.copyProperties(dto, adminUser);
        updateById(adminUser);
        // 重新生成用户信息到Sa-Session中
        LoginVo loginVo = BaseContext.getLoginVo();
        BeanUtils.copyProperties(dto, loginVo);
        BaseContext.setLoginVo(loginVo);
    }

    /**
     * * 更新本地用户密码
     *
     * @param dto 更新本地用户密码DTO
     */
    @Override
    public void updateUserPasswordByLocalUser(@Valid UserPasswordUpdateDto dto) {
        Long userId = BaseContext.getUserId();
        String oldPassword = dto.getOldPassword();
        String password = dto.getNewPassword();
        String confirmPassword = dto.getConfirmPassword();

        // 判断是否存在这个用户
        User user = getOne(Wrappers.<User>lambdaQuery().eq(User::getId, userId));
        if (user == null) throw new CustomException(ResultCodeEnum.USER_IS_EMPTY);

        // 校验原密码
        if (!passwordEncoder.matches(oldPassword, user.getPassword()))
            throw new CustomException(ResultCodeEnum.OLD_PASSWORD_ERROR);

        // 判断两次密码是否一致
        if (!confirmPassword.equals(password))
            throw new CustomException(ResultCodeEnum.CONFIRM_PASSWORD_ERROR);

        // 对密码加密
        String encode = passwordEncoder.encode(password);

        // 判断新密码是否与旧密码相同
        if (user.getPassword().equals(encode))
            throw new CustomException(ResultCodeEnum.UPDATE_NEW_PASSWORD_SAME_AS_OLD_PASSWORD);

        // 更新用户
        User adminUser = new User();
        adminUser.setId(userId);
        adminUser.setPassword(encode);
        updateById(adminUser);

        // 重新生成用户信息到Redis中
        user.setPassword(adminUser.getPassword());
        userUtil.buildUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME);
    }
    
    /**
     * * 更新本地用户头像
     *
     * @param file 用户头像文件
     */
    @SneakyThrows
    @Override
    public void updateAvatarByLocalUser(MultipartFile file) {
        Long userId = BaseContext.getUserId();

        // 判断是否存在这个用户
        User user = getOne(Wrappers.<User>lambdaQuery().eq(User::getId, userId));
        if (user == null) throw new CustomException(ResultCodeEnum.USER_IS_EMPTY);

        // 直接使用MinioUtil上传头像，不使用filesService.upload
        MinioFilePath minioFilePath = minioUtil.uploadObjectReturnFilePath(file, MinioConstant.avatar);
        String avatarPath = minioFilePath.getBucketNameFilepath();

        // 更新用户
        User adminUser = new User();
        adminUser.setId(userId);
        adminUser.setAvatar(avatarPath);
        updateById(adminUser);

        // 重新生成用户信息
        user.setAvatar(adminUser.getAvatar());
        userUtil.buildUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME);
    }

    /**
     * * 用户信息 服务实现类
     *
     * @param pageParams 用户信息分页查询page对象
     * @param dto        用户信息分页查询对象
     * @return 查询分页用户信息返回对象
     */
    @Override
    public PageResult<AdminUserVo> getAdminUserList(Page<User> pageParams, AdminUserDto dto) {
        IPage<ViewUserDept> page = baseMapper.selectListByPage(pageParams, dto);

        List<AdminUserVo> voList = page.getRecords().stream()
                .map(adminUser -> {
                    // 如果存在用户头像，则设置用户头像
                    String avatar = userUtil.checkGetUserAvatar(adminUser.getAvatar());

                    AdminUserVo adminUserVo = new AdminUserVo();
                    BeanUtils.copyProperties(adminUser, adminUserVo);
                    adminUserVo.setAvatar(avatar);
                    return adminUserVo;
                })
                .filter(adminUserVo -> !adminUserVo.getId().equals(1L))
                .toList();

        return PageResult.<AdminUserVo>builder()
                .list(voList)
                .pageNo(page.getCurrent())
                .pageSize(page.getSize())
                .total(page.getTotal())
                .build();
    }

    /**
     * * 添加用户信息
     * 需要确认用户名-username是唯一的
     * 需要确认邮箱-email是唯一的
     *
     * @param dto 用户信息添加
     */
    @Override
    public void addAdminUser(@Valid AdminUserAddDto dto) {
        // 添加用户信息
        User user = new User();
        BeanUtils.copyProperties(dto, user);
        
        // 对密码进行加密
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        
        // 保存用户信息
        save(user);

        // 插入用户部门关系表
        Long userId = user.getId();
        Long deptId = dto.getDeptId();
        UserDept userDept = new UserDept();
        userDept.setDeptId(deptId);
        userDept.setUserId(userId);

        // 插入分配后的用户内容
        userDeptMapper.insert(userDept);

        // 在图数据库中添加用户
        UserNode userNode = new UserNode();
        userNode.setName(user.getUsername());
        userNode.setUserId(user.getId());
        userRepository.save(userNode);
    }

    /**
     * 更新用户信息
     * 如果更新了用户名需要用户重新登录
     *
     * @param dto 用户信息更新
     */
    @Override
    public void updateAdminUser(AdminUserUpdateDto dto) {
        // 部门Id
        Long deptId = dto.getDeptId();
        Long userId = dto.getId();

        // 判断更新内容是否存在
        User adminUser = getOne(Wrappers.<User>lambdaQuery().eq(User::getId, userId));
        if (adminUser == null) throw new CustomException(ResultCodeEnum.DATA_NOT_EXIST);

        // 不允许更新用户名
        dto.setUsername(adminUser.getUsername());


        // 更新用户
        adminUser = new User();
        BeanUtils.copyProperties(dto, adminUser);
        updateById(adminUser);

        // 更新用户部门
        UserDept userDept = new UserDept();
        userDept.setDeptId(deptId);
        userDept.setUserId(userId);

        // 删除这个用户部门关系下所有
        userDeptMapper.deleteBatchIdsByUserIdWithPhysics(List.of(userId));

        // 插入分配后的用户内容
        userDeptMapper.insert(userDept);

        // 重新生成用户信息
        BeanUtils.copyProperties(dto, adminUser);
        userUtil.buildUserVo(adminUser, RedisUserConstant.REDIS_EXPIRATION_TIME);
    }

    /**
     * 删除|批量删除用户信息
     *
     * @param ids 删除id列表
     */
    @Override
    public void deleteAdminUser(List<Long> ids) {
        // 判断数据请求是否为空
        if (ids.isEmpty()) throw new CustomException(ResultCodeEnum.REQUEST_IS_EMPTY);

        // 根据用户Id列表查询用户角色
        List<Role> list = roleMapper.selectListByUserIds(ids);
        List<Role> roleList = list.stream().filter(role -> role.getRoleCode().equals("admin") || ids.contains(1L)).toList();
        if (!roleList.isEmpty()) throw new CustomException(ResultCodeEnum.ADMIN_ROLE_CAN_NOT_DELETED);

        // 逻辑删除
        removeBatchByIds(ids);
        // 物理删除用户
//         baseMapper.deleteBatchIdsWithPhysics(ids);

        // 删除部门相关
        userDeptMapper.deleteBatchIdsByUserIdWithPhysics(ids);

        // 删除用户角色相关
        userRoleMapper.deleteBatchIdsByUserIdsWithPhysics(ids);

        // 删除图数据库中用户
        userRepository.deleteAllById(ids);
    }
    
    @Override
    public Object getUserInfo(Long userId) {
        if (userId == null) {
            return null;
        }
        // 获取用户信息
        User user = this.baseMapper.selectById(userId);
        if (user == null) {
            throw new CustomException(ResultCodeEnum.USER_IS_EMPTY);
        }
        
        // 返回用户信息，这里直接返回User对象
        // 如果需要返回特定字段，可以创建一个DTO对象
        return user;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int importUsersByExcel(MultipartFile file, Long deptId) throws IOException {
        if (file == null || file.isEmpty()) {
            throw new CustomException(ResultCodeEnum.REQUEST_IS_EMPTY);
        }
        
        if (deptId == null) {
            throw new CustomException("部门ID不能为空");
        }
        
        // 创建计数变量
        final int[] count = {0};
        
        try {
            // 使用EasyExcel读取Excel文件
            ExcelReader excelReader = EasyExcel.read(
                    file.getInputStream(),
                    UserImportExcelDto.class,
                    new UserImportListener(this, deptId)
            ).build();
            
            // 读取第一个sheet
            ReadSheet readSheet = EasyExcel.readSheet(0).build();
            excelReader.read(readSheet);
            
            // 这里会存在多线程导入的情况，可能无法准确获取导入的总数
            // 因此返回一个默认成功标记
            return 1;
        } catch (Exception e) {
            log.error("导入Excel文件失败", e);
            throw new CustomException("导入Excel文件失败：" + e.getMessage());
        }
    }
}
